import { AopPointcut, PointcutOption } from "../../type/aop2type"
import { Util } from "../../src/util";
const util = require('util');
import { log } from "console";

export class aop2 {
    /**
     * 存储切点
     */
    private static pointcutMap: Map<string, AopPointcut> = new Map();
    /**
     * 注册切面
     */
    private static registAspect: Map<string, AopPointcut> = new Map();

    /**
     * 
     * @param cfg 表达式数组
     * @returns 表达式转换为正则表达式的结果数组
     */
    private static expressionToReg(cfg: Array<string>): RegExp[] {
        let res: RegExp[] = [];
        for (let i = 0; i < cfg.length; i++) {
            res.push(Util.toReg(cfg[i]))
        }
        return res
    }
    /**
     * 
     * @param cfg 要注册的切点信息，包括切点的类、表达式、切点名
     */
    public static addPointCut(cfg: PointcutOption): void {
        if (!this.registAspect.has(cfg.clazz)) {
            let temp: AopPointcut = {
                PointcutName: cfg.PointcutName,
                aspectClazz: cfg.clazz,
                expressions: [],
                advices: []
            }
            this.registAspect.set(cfg.clazz.name, temp)
            this.addExpress(cfg.clazz.name as string, cfg.expression)
        }
    }
    /**
     * 
     * @param clazz 切点类
     * @param expressions 切点表达式
     * @returns 
     */
    public static addExpress(clazz: string, expressions: Array<string>): void {
        if (!this.registAspect.has(clazz))
            return
        const currentPointcut = this.registAspect.get(clazz)
        if (currentPointcut.expressions.length === 0)
            currentPointcut.expressions = this.expressionToReg(expressions)
        else
            currentPointcut.expressions.concat(this.expressionToReg(expressions))
    }
    /**
     * 
     * @param clazz 切点类名
     * @param advices 切点的通知数组
     * @returns 
     */
    public static addAdvice(clazz: string, advices: Array<Function>): void {
        if (!this.registAspect.has(clazz))
            return
        const currentPointcut = this.registAspect.get(clazz)
        currentPointcut.advices = advices
    }

    /**
     * 
     * @param clazz 要注册的切面的类名
     * @returns 将registAspect中class对应的切面放入切点Mapz中，并删除
     */
    public static addAspect(clazz: any | string) {

        //注册切点时还要放在IoC工厂中

        if (typeof clazz === 'function') {
            clazz = clazz.name
        }
        if (!this.registAspect.has(clazz))
            return
        const pc = this.registAspect.get(clazz)
        this.pointcutMap.set(clazz + "." + pc.PointcutName, pc)
        this.registAspect.delete(clazz)

        this.proxyAll()
    }

    /**
     * proxyAll 为所有切点设置代理
     */
    public static proxyAll() {
        if (!this.pointcutMap || this.pointcutMap.size === 0) {
            return
        }
        this.pointcutMap.forEach(item => {
            this.proxyPointcutOne(item.aspectClazz, item.PointcutName)
        })
    }


    
    private static proxyPointcutOne(target: any, PointcutName: string) {
        let a: AopPointcut
        for (a of this.pointcutMap.values()) {
            for (let temp of a.expressions) {
                let tag: RegExp = temp
                Object.getOwnPropertyNames(target.prototype).forEach(key => {
                    // log("!1", key)
                    if (key === 'constructor' || typeof target.prototype[key] !== 'function') {
                        return
                    }
                    const name = target.name + '.' + key;    //生成一个全名，由类的名字和属性名组成
                    log(name)
                    if (tag.test(name)) {
                        target.prototype[key] = this.invoke(this.pointcutMap.get(target.name + "." + PointcutName), key)
                    }
                });
            }
        }
    }

    private static invoke(target: AopPointcut, methodName: string) {
        let func = target.aspectClazz.prototype[methodName]
        if (!func) {
            return;
        } else if (util.types.isAsyncFunction(func)) {
            return async function (...args) {
                let params = args
                console.log("11", params);
                //执行前置通知
                target.advices[0]();
                try {
                    await func(params)
                    target.advices[2]();
                } catch (error) {
                    target.advices[3]();
                    throw error;
                }
                target.advices[1]();
            }
        } else {
            return function (...args) {
                let params = args
                console.log("11", params);
                //执行前置通知
                target.advices[0]();
                try {
                    let result = func(params)
                    if (util.types.isPromise(result)) {
                        (<Promise<unknown>>result).then(() => {
                            target.advices[2]();
                        });
                    } else {
                        target.advices[2]();
                    }
                } catch (error) {
                    target.advices[3]();
                    throw error;
                }
                target.advices[1]();
            }
        }
    }
}
